<?php
/**
 * WB_S3RawResponse class
 * 
 * @author Jean-Lou Dupont
 * @version @@package-version@@
 * @id $Id: S3RawResponse.php 155 2008-03-15 02:36:20Z jldupont $
 */

/**
 * IF HTTP code !== 200 THEN  
 * 	content-type = 'application/xml',  body => error msg
 * 
 *  retrieve ERROR_MSG
 *  look-up ERROR_TYPE
 *  IF ERROR_TYPE == [ throw exception ]
 * 
 * 
 * IF HTTP code === 200 THEN
 *  
 * 
 */


/* 
 * EXAMPLE of "GET /" 
 *
["head"]=>
  array(6) {
    ["x-amz-id-2"]=> string(64) "c+b4tsrzioIl1AFOMeI7sWUlL7BcboU4CCCLTeivQXnNpx3XJTGeLXpuPxY1auFZ"
    ["x-amz-request-id"]=> string(16) "4F0F7DB35BAD5956"
    ["date"]=> string(29) "Tue, 11 Mar 2008 18:37:59 GMT"
    ["content-type"]=> string(15) "application/xml"
    ["transfer-encoding"]=> string(7) "chunked"
    ["server"]=> string(8) "AmazonS3"
  }
  ["body"]=> string(2459) "<?xml version="1.0" encoding="UTF-8"?>
             <ListAllMyBucketsResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Owner><ID>1f9746f3841a1ee79d0e9d2037fc7678e067d62b4bc741179b6a2e59482e913b</ID><DisplayName>jeanlou_dupont</DisplayName></Owner><Buckets><Bucket><Name>Charles-Olivier.Dupont</Name><CreationDate>2007-10-30T00:03:46.000Z</CreationDate></Bucket><Bucket><Name>Dictionary</Name><CreationDate>2007-10-30T00:02:57.000Z</CreationDate></Bucket><Bucket><Name>Isabelle.Vezina</Name><CreationDate>2007-10-30T00:03:30.000Z</CreationDate></Bucket><Bucket><Name>Jean-Lou.Dupont</Name><CreationDate>2007-10-26T19:02:28.000Z</CreationDate></Bucket><Bucket><Name>Julianne.Dupont</Name><CreationDate>2007-10-30T00:03:37.000Z</CreationDate></Bucket><Bucket><Name>Zend</Name><CreationDate>2007-10-30T16:49:35.000Z</CreationDate></Bucket><Bucket><Name>bizzwiki</Name><CreationDate>2007-10-11T13:19:53.000Z</CreationDate></Bucket><Bucket><Name>bluecortex</Name><CreationDate>2007-10-11T13:20:00.000Z</CreationDate></Bucket><Bucket><Name>cortex</Name><CreationDate>2007-10-27T11:40:51.000Z</CreationDate></Bucket><Bucket><Name>greencortex</Name><CreationDate>2007-10-11T13:20:05.000Z</CreationDate></Bucket><Bucket><Name>jldupont</Name><CreationDate>2007-10-11T13:19:19.000Z</CreationDate></Bucket><Bucket><Name>jldupont-mm</Name><CreationDate>2007-12-07T22:01:13.000Z</CreationDate></Bucket><Bucket><Name>jldupont_domains</Name><CreationDate>2007-11-02T01:26:32.000Z</CreationDate></Bucket><Bucket><Name>jldupont_logs</Name><CreationDate>2007-10-26T19:03:14.000Z</CreationDate></Bucket><Bucket><Name>jldupont_registry</Name><CreationDate>2007-10-26T19:00:48.000Z</CreationDate></Bucket><Bucket><Name>jldupont_test</Name><CreationDate>2007-10-11T02:09:42.000Z</CreationDate></Bucket><Bucket><Name>jldupont_users</Name><CreationDate>2007-10-26T19:00:59.000Z</CreationDate></Bucket><Bucket><Name>jldupont_wiki</Name><CreationDate>2007-09-29T11:16:43.000Z</CreationDate></Bucket><Bucket><Name>pear</Name><CreationDate>2007-10-11T13:19:39.000Z</CreationDate></Bucket><Bucket><Name>phpmylinux</Name><CreationDate>2007-10-11T13:21:13.000Z</CreationDate></Bucket><Bucket><Name>smarty</Name><CreationDate>2008-01-16T02:39:34.000Z</CreationDate></Bucket><Bucket><Name>uber-cortex</Name><CreationDate>2007-10-26T19:02:09.000Z</CreationDate></Bucket><Bucket><Name>virtual-cortex</Name><CreationDate>2007-10-26T19:02:03.000Z</CreationDate></Bucket></Buckets></ListAllMyBucketsResult>"
 */

 //{{include
  #require_once 'web-bloks/BaseClass/BaseClass.php';
 //include}}
 
 class WB_S3RawResponse
 	extends WB_BaseClass
 {
 	/**
 	 * Error Code Map
 	 */
 	static $errorCodeMap = array();
	
	/**
	 * Requested resource
	 */	
	var $resource = null;	
 	/**
 	 * Request Header that generated
 	 * this response.
 	 */
 	var $req = null;
	
	/**
	 * HTTP response code
	 */
 	var $code = null;
	
	/**
	 * Response Header
	 */
	var $head = null;
	
	/**
	 * Response Body
	 */
	var $body = null;
	
	/**
	 * Error message
	 */	
	var $msg = null;	
	
 	/**
 	 * Constructor
 	 */
	public function __construct( $params = null )
	{
		parent::__construct( $params );
	}
	// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%		
	// WB_BaseClass
	// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%				
	/**
	 * @see WB_BaseClass::getClassPath
	 */
	public function getClassPath()
	{
		return 'web-bloks/AmazonS3/responses';
	}
	/**
	 * @see WB_BaseClass::getClassVersion
	 * @return 
	 */
	public function getClassVersion()
	{
		return '$Id: S3RawResponse.php 155 2008-03-15 02:36:20Z jldupont $';
	}
	// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%		
	// PUBLIC Interface
	// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
	/**
	 * Returns the string that identifies
	 * the resource that was requested.
	 *  
	 * @return 
	 */
	public function getResource()
	{
		return $this->resource;
	}
	/**
	 * Initializes the raw response
	 * 
	 * @return mixed
	 * @param $resource string
	 * @param $req mixed
	 * @param $code string
	 * @param $head mixed
	 * @param $body mixed
	 */
	public function init( &$resource, &$req, &$code, &$head, &$body )
	{
		$this->resource = $resource;		
		$this->req  = $req;
		$this->code = $code;
		$this->head = $head;
		$this->body = $body;	
		
		$msg = null;
		
		if ( $this->isError( ) )
			if ( $this->causeException( $msg ) )
				$this->throwException( "$msg (${resource})" );
	}
	/**
	 * Returns the raw response body.
	 * @return string
	 */
	public function getBody()
	{
		return $this->body;
	}
	/**
	 * Returns the raw HTTP response code
	 * @return string
	 */
	public function getCode()
	{
		return $this->code;
	}
	/**
	 * Returns the error message
	 * @return string
	 */
	public function getMsg()
	{
		return $this->msg;
	}	
	/**
	 * Returns the raw HTTP headers
	 * @return array
	 */
	public function getHead()
	{
		return $this->head;	
	}
	/**
	 * Determines if the HTTP code indicates an error
	 * 
	 * @return boolean false if no error
	 * @return string if error
	 * @param $code string
	 */
	public function isError( )
	{
		// e.g. code 204 can be returned by the DELETE method
		// even if no object was deleted
		
		return ( $this->code >= 300 );	
	}
	// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%		
	// INTERNAL methods
	// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%	
	/**
	 * Verifies if the returned code should
	 * trigger an exception.
	 * 
	 * @return 
	 * @param $code Object
	 */
	protected function causeException( &$msg )
	{
		// does the raw response include
		// an XML body?
		// IF not XML, then we can't know here
		if (!$this->isXML())
			return false;
			
		// find the error code entry
		// based on the S3 error message
		$msg = null;			
		$entry = $this->findEntry( $msg );
		
		// retrieve which type of code
		// we are dealing with
		$type = $entry[ 't' ];

		// only the 'O' type generates an error
		// all the other types generate an exception		
		return ( $type !== 'O' );
	}
	protected function findEntry( &$msg )	
	{
		$this->msg = $this->extractErrorMessage();
		
		if ( $this->msg === false )
			$this->throwException( "couldn't locate error msg in raw response body" );
		
		$entry = null;
		if ( isset( self::$errorCodeMap[ $this->msg ] ) )	
			$entry = self::$errorCodeMap[ $this->msg ];
			
		return $entry;			
	}
	/**
	 * 
	 * @return 
	 */
	protected function isXML()
	{
		if ( !isset( $this->head['content-type'] ) )
			return false;
			
		// paranoia
		$ct = strtolower( $this->head[ 'content-type' ] );
		
		// application/xml , text/xml
		$result = strpos( $ct, "xml" );

		return !( $result === false );			
	}
	/**
	 * Extracts, if possible, the error message string
	 * from the body of the response.
	 * 
	 * @return false if no message found
	 * @return string if message found
	 */	
	protected function extractErrorMessage( )
	{
		// shortcut
		$body = $this->body;
		
		$r = preg_match(	"/\<Code\>(.*?)\<\/Code\>/i", 
							$body, 
							$matches );
		
		if(($r !== false) && ($r !== 0))
			return $matches[1];

		return false;		
	}
	
 } // end class declaration
 
 /**
  * Initialization of the error code map.
  * This map serves as a guide on how to handle
  * each error code.
  * 
  * c: HTTP level code
  *                                    Effect:
  * t: type                            ===============
  *    U: User                         Exception
  *    S: Service                      Exception
  *    O: Object                       Error to client
  *    -: unsupported                  Exception
  *       (i.e. should not occur)
  *    ?: not classified ...           Exception
  */
 WB_S3RawResponse::$errorCodeMap = array(
	'AccessDenied' 						       => array( 'c' => 403, 't' => 'U' ),
	'AccountProblem'                           => array( 'c' => 403, 't' => 'S' ),
	'AmbiguousGrantByEmailAddress'             => array( 'c' => 400, 't' => '-' ),
	'BadDigest'                                => array( 'c' => 400, 't' => 'U' ),
	'BucketAlreadyExists'                      => array( 'c' => 409, 't' => 'O' ),
	'BucketAlreadyOwnedByYou'                  => array( 'c' => 409, 't' => 'O' ),
	'BucketNotEmpty'                           => array( 'c' => 409, 't' => 'O' ),
	'CredentialsNotSupported'                  => array( 'c' => 400, 't' => '-' ),
	'CrossLocationLoggingProhibitted'          => array( 'c' => 403, 't' => '-' ),
	'EntityTooSmall'                           => array( 'c' => 400, 't' => 'U' ),
	'EntityTooLarge'                           => array( 'c' => 400, 't' => 'U' ),
	'ExpiredToken'                             => array( 'c' => 400, 't' => 'U' ),
	'IncompleteBody'                           => array( 'c' => 400, 't' => 'U' ),
	'IncorrectNumberOfFilesInPostRequestPOST'  => array( 'c' => 400, 't' => 'U' ),
	'InlineDataTooLarge'                       => array( 'c' => 400, 't' => 'U' ),
	'InternalError'                            => array( 'c' => 500, 't' => 'S' ),
	'InvalidAccessKeyId'                       => array( 'c' => 403, 't' => 'U' ),
	'InvalidAddressingHeader'                  => array( 'c' => 000, 't' => 'U' ),
	'InvalidArgument'                          => array( 'c' => 400, 't' => 'U' ),
	'InvalidBucketName'                        => array( 'c' => 400, 't' => 'U' ),
	'InvalidDigest'                            => array( 'c' => 400, 't' => 'U' ),
	'InvalidLocationConstraint'                => array( 'c' => 400, 't' => 'U' ),
	'InvalidPayer'                             => array( 'c' => 403, 't' => '-' ),
	'InvalidPolicyDocument'                    => array( 'c' => 400, 't' => '-' ),
	'InvalidRange'                             => array( 'c' => 416, 't' => '?' ),
	'InvalidSecurity'                          => array( 'c' => 403, 't' => '?' ),
	'InvalidSOAPRequest'                       => array( 'c' => 400, 't' => 'N' ),
	'InvalidStorageClass'                      => array( 'c' => 400, 't' => '?' ),
	'InvalidTargetBucketForLogging'            => array( 'c' => 400, 't' => '-' ),
	'InvalidToken'                             => array( 'c' => 400, 't' => 'U' ),
	'InvalidURI'                               => array( 'c' => 400, 't' => 'O' ),
	'KeyTooLong'                               => array( 'c' => 400, 't' => '?' ),
	'MalformedACLError'                        => array( 'c' => 400, 't' => 'U' ),
	'MalformedXML'                             => array( 'c' => 400, 't' => '?' ),
	'MaxMessageLengthExceeded'                 => array( 'c' => 400, 't' => 'U' ),
	'MaxPostPreDataLengthExceededError'        => array( 'c' => 400, 't' => 'U' ),
	'MetadataTooLarge'                         => array( 'c' => 400, 't' => 'U' ),
	'MethodNotAllowed'                         => array( 'c' => 405, 't' => 'U' ),
	'MissingAttachment'                        => array( 'c' => 000, 't' => 'U' ),
	'MissingContentLength'                     => array( 'c' => 411, 't' => 'U' ),
	'MissingSecurityElement'                   => array( 'c' => 400, 't' => 'U' ),
	'MissingSecurityHeader'                    => array( 'c' => 400, 't' => 'U' ),
	'NoLoggingStatusForKey'                    => array( 'c' => 400, 't' => '-' ),
	'NoSuchBucket'                             => array( 'c' => 404, 't' => 'O' ),
	'NoSuchKey'                                => array( 'c' => 404, 't' => 'O' ),
	'NotImplemented'                           => array( 'c' => 501, 't' => 'U' ),
	'NotSignedUp'                              => array( 'c' => 403, 't' => 'U' ),
	'OperationAborted'                         => array( 'c' => 409, 't' => 'S' ),
	'PermanentRedirect'                        => array( 'c' => 301, 't' => 'U' ),
	'PreconditionFailed'                       => array( 'c' => 412, 't' => '?' ),
	'Redirect'                                 => array( 'c' => 307, 't' => 'O' ),
	'RequestIsNotMultiPartContent'             => array( 'c' => 400, 't' => 'U' ),
	'RequestTimeout'                           => array( 'c' => 400, 't' => 'S' ),
	'RequestTimeTooSkewed'                     => array( 'c' => 403, 't' => 'U' ),
	'RequestTorrentOfBucketError'              => array( 'c' => 400, 't' => '-' ),
	'SignatureDoesNotMatch'                    => array( 'c' => 403, 't' => 'U' ),
	'SlowDown'                                 => array( 'c' => 503, 't' => '?' ),
	'TemporaryRedirect'                        => array( 'c' => 307, 't' => 'U' ),
	'TokenRefreshRequired'                     => array( 'c' => 400, 't' => 'U' ),
	'TooManyBuckets'                           => array( 'c' => 400, 't' => 'O' ),
	'UnexpectedContent'                        => array( 'c' => 400, 't' => 'U' ),
	'UnresolvableGrantByEmailAddress'          => array( 'c' => 400, 't' => 'N' ),
	'UserKeyMustBeSpecified'                   => array( 'c' => 400, 't' => 'U' ),
 );
